package sim1011.starter;

import jade.core.Profile;
import jade.core.ProfileImpl;
import jade.core.Runtime;
import jade.wrapper.AgentContainer;
import jade.wrapper.AgentController;
import jade.wrapper.ControllerException;
import jade.wrapper.StaleProxyException;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Random;

import sim1011.components.Component;
import sim1011.components.PowerVampire;
import sim1011.nodes.resources.NodeType1;
import sim1011.nodes.resources.NodeType2;
import sim1011.nodes.resources.NodeType3;
import sim1011.nodes.resources.NodeType4;
import sim1011.nodes.resources.NodeTypeM;
import sim1011.poltergeist.Poltergeist;
import sim1011.poltergeist.ResourceManager;
import sim1011.status.Status;

public class EctoMobileStarter implements java.io.Serializable  {
	private static final long serialVersionUID = 5485627470433368686L;
	public static final int HOW_MANY_MEASURES_ON_RUN = 30;
	private static final int HOW_MANY_RUN = 1;
	private static final String politicName = "cost1dtraffic1d";
	private static final int RUN_NUMBER = 30;
	// Supporto per 4 (+1) tipi di nodi co caratteristiche diverse.
	public static final int NUM_NODES_1 = 3; //3
	public static final int NUM_NODES_2 = 2; //2
	public static final int NUM_NODES_3 = 2; //2
	public static final int NUM_NODES_4 = 5; //5
	public static final int NUM_NODES_M = 1; 
	public static final int NUM_COMPONENTS = 100;
	public static final int TOTAL_NODES = NUM_NODES_1 + NUM_NODES_2 + 
		NUM_NODES_3 + NUM_NODES_4 + NUM_NODES_M;
	public static final int MAX_DELTA_MOVE = 30;
	public static final int MAX_NETWORK_LINK_QOS = 100;
	public static final int MIN_NETWORK_LINK_QOS = 0;
	public static final float MOBILE_POWER_THRESHOLD = 13.0f;
	public static final float DRAIN_POWER_WITH_VAMPIRE = 0.5f;
	public static final long DRAIN_POWER_TIME = 1000; // ms
	public static final long MOVE_NODE_TIME = 2000; // ms
	public static final long PHOTO_TIME = 2000; // ms
	public static final String MOBILE_NODE_NAME = "Node.M";
	public static final int MIGRATION_OVERHEAD = 1;
	//parametri di costo batteria per operazione
	public static final float BATTERY_FOR_MEMORY_REQ = 0.002f;
	public static final float BATTERY_FOR_OPERATION_CPU = 0.01f;
	public static final float BATTERY_FOR_TRANSFER = 0.18f;
	public static final float MAX_COMP_SIZE_ON_MOBILE = 16.128f;
	public static float COST_P = 1.0f;
	public static float TRAFFIC_P = 1.0f;	
	public static float BATTERY_P = 0.0f;
	public static float TOTAL_EXEC_P = 0.0f;
	public static float EXEC_MOBILE_P = 0.0f;
	public static float DUMMY_P = 0.0f;
	public static float DYNAMIC_P = 1.0f;
	public static float DO_NOTHING_P = 0.0f;
	public static final float CPU_WEIGHT = 0.1f;
	public static final float MEMORY_WEIGHT = 0.3f;
	public static Random r = new Random(System.currentTimeMillis()); // seed random
	public static final float MIN_RANK_VAL = 0f;
	public static final float MAX_RANK_VAL = 100f;
	public static final int AVAILABLE_NODE_SCORE_TH = 60;
	public static final String batteryFile = "simulations/measures/battery_level_"+politicName+"_"+EctoMobileStarter.RUN_NUMBER+".txt";
	public static final String trafficFile = "simulations/measures/traffic_level_"+politicName+"_"+EctoMobileStarter.RUN_NUMBER+".txt";
	public static final String globalCostFile = "simulations/measures/global_cost_"+politicName+"_"+EctoMobileStarter.RUN_NUMBER+".txt";
	public static final String throughputFile = "simulations/measures/throughput_level_"+politicName+"_"+EctoMobileStarter.RUN_NUMBER+".txt";
	public static final String mobileCPULoadFile = "simulations/measures/mobileCpuLoad_level_"+politicName+"_"+EctoMobileStarter.RUN_NUMBER+".txt";
	public static final String mobileMEMORYLoadFile = "simulations/measures/mobileMemoryLoad_level_"+politicName+"_"+EctoMobileStarter.RUN_NUMBER+".txt";
	private ArrayList<AgentContainer> agentContainersList;
	private ArrayList<ResourceManager> resourceManagersList;
	private ArrayList<Component> componentsList; 
	private ArrayList<AgentController> agentControllersList;
	private ArrayList<String> resourceManagerNamesList;
	public static Thread mainThread;
	
	public static float COST_INCREMENT = 1.0f;
	public static float TRAFFIC_INCREMENT = 0.5f;
	public static float COST_INCREMENT_THRESHOLD_PERC = 0.05f;
	public static float TRAFFIC_INCREMENT_THRESHOLD_PERC = 0.3f;
	
	
	public static void main(String [] args) throws InterruptedException {
		//mainThread = Thread.currentThread();
		for (int i=0; i<HOW_MANY_RUN; ++i) {
			EctoMobileStarter starter = new EctoMobileStarter();
			try {
				starter.startSystem();
				//mainThread.wait();
				//starter.stopSystem();
			} catch (StaleProxyException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
	
	private void stopSystem() throws StaleProxyException {
		for (int i=0; i<agentControllersList.size(); ++i)
			agentControllersList.get(i).kill();
		for (int i=0; i<agentContainersList.size(); ++i)
			agentContainersList.get(i).kill();
	}

	public void startSystem() throws IOException, StaleProxyException {
		//inizializzazione del logger
		Logger.inizializeFileWriter();
		
		Runtime t = Runtime.instance();
		Profile mainProfile = new ProfileImpl();
		mainProfile.setParameter(Profile.LOCAL_HOST, "localhost");
		mainProfile.setParameter(Profile.PLATFORM_ID, "ecto-platform");
		mainProfile.setParameter(Profile.CONTAINER_NAME, "PoltergeistNode");
		
		agentContainersList = new ArrayList<AgentContainer>();		
		//lista dei resource manager
		resourceManagersList = new ArrayList<ResourceManager>();
		//lista dei componenti
		componentsList = new ArrayList<Component>();		
		//creazione dello stato
		Status status = new Status();		
		// lista di tutti i controller degli agenti (poltergeist,resource manager, vampiro,component)
		agentControllersList = new ArrayList<AgentController>();
		// lista dei nomi di tutti i resource manager
		resourceManagerNamesList = new ArrayList<String>();
		
		// poltergeist
		Poltergeist poltergeist = new Poltergeist();
		// vampiro
		PowerVampire powerVampire = new PowerVampire();
		// lista degli agent controller
		
		//creazione del main container
		AgentContainer mainContainer = t.createMainContainer(mainProfile);
		agentContainersList.add(mainContainer);
		
		//creazione dei container per i nodi fissi
		for(int i=0; i<TOTAL_NODES-1; i++) {
			Profile profile = new ProfileImpl();
			profile.setParameter(Profile.LOCAL_HOST, "localhost");			
			profile.setParameter(Profile.PLATFORM_ID, "ecto-platform");			
			profile.setParameter(Profile.CONTAINER_NAME, "Node." + i);
			AgentContainer container = t.createAgentContainer(profile);
			agentContainersList.add(container);
		}
		//creazione del container per il nodo mobile 
		Profile mobileProfile = new ProfileImpl();
		mobileProfile.setParameter(Profile.LOCAL_HOST, "localhost");			
		mobileProfile.setParameter(Profile.PLATFORM_ID, "ecto-platform");			
		mobileProfile.setParameter(Profile.CONTAINER_NAME, "Node.M");
		AgentContainer container = t.createAgentContainer(mobileProfile);
		agentContainersList.add(container);
		
		//creazione dei resource manager		
		for (int i=0; i<NUM_NODES_1; ++i) {
			ResourceManager resourceManagerAgent = new ResourceManager();
			resourceManagerAgent.setNOMINAL_CPU(NodeType1.NOMINAL_CPU);
			resourceManagerAgent.setNOMINAL_MEMORY(NodeType1.NOMINAL_MEMORY);
			resourceManagerAgent.setCostCPU(NodeType1.costCPU);
			resourceManagerAgent.setCostMemory(NodeType1.costMEMORY);			
			status.addNewResourceManager("Node."+resourceManagersList.size(), resourceManagerAgent);
			resourceManagerNamesList.add("Node."+resourceManagersList.size());
			resourceManagersList.add(resourceManagerAgent);			
		}		
		for (int i=0; i<NUM_NODES_2; ++i) {
			ResourceManager resourceManagerAgent = new ResourceManager();
			resourceManagerAgent.setNOMINAL_CPU(NodeType2.NOMINAL_CPU);
			resourceManagerAgent.setNOMINAL_MEMORY(NodeType2.NOMINAL_MEMORY);
			resourceManagerAgent.setCostCPU(NodeType2.costCPU);
			resourceManagerAgent.setCostMemory(NodeType2.costMEMORY);
			status.addNewResourceManager("Node."+resourceManagersList.size(), resourceManagerAgent);
			resourceManagerNamesList.add("Node."+resourceManagersList.size());
			resourceManagersList.add(resourceManagerAgent);
		}
		for (int i=0; i<NUM_NODES_3; ++i) {
			ResourceManager resourceManagerAgent = new ResourceManager();
			resourceManagerAgent.setNOMINAL_CPU(NodeType3.NOMINAL_CPU);
			resourceManagerAgent.setNOMINAL_MEMORY(NodeType3.NOMINAL_MEMORY);
			resourceManagerAgent.setCostCPU(NodeType3.costCPU);
			resourceManagerAgent.setCostMemory(NodeType3.costMEMORY);
			status.addNewResourceManager("Node."+resourceManagersList.size(), resourceManagerAgent);
			resourceManagerNamesList.add("Node."+resourceManagersList.size());
			resourceManagersList.add(resourceManagerAgent);
		}
		for (int i=0; i<NUM_NODES_4; ++i) {
			ResourceManager resourceManagerAgent = new ResourceManager();
			resourceManagerAgent.setNOMINAL_CPU(NodeType4.NOMINAL_CPU);
			resourceManagerAgent.setNOMINAL_MEMORY(NodeType4.NOMINAL_MEMORY);
			resourceManagerAgent.setCostCPU(NodeType4.costCPU);
			resourceManagerAgent.setCostMemory(NodeType4.costMEMORY);			
			status.addNewResourceManager("Node."+resourceManagersList.size(), resourceManagerAgent);
			resourceManagerNamesList.add("Node."+resourceManagersList.size());
			resourceManagersList.add(resourceManagerAgent);
		}
		
		//creazione del resource manager del nodo mobile
		ResourceManager resourceManagerMobileAgent = new ResourceManager();
		resourceManagerMobileAgent.setNOMINAL_CPU(NodeTypeM.NOMINAL_CPU);
		resourceManagerMobileAgent.setNOMINAL_MEMORY(NodeTypeM.NOMINAL_MEMORY);
		resourceManagerMobileAgent.setCostCPU(NodeTypeM.costCPU);
		resourceManagerMobileAgent.setCostMemory(NodeTypeM.costMEMORY);
		resourceManagerMobileAgent.setBatteryLevel(100.0f);
		status.addNewResourceManager(EctoMobileStarter.MOBILE_NODE_NAME, resourceManagerMobileAgent);
		resourceManagerNamesList.add(EctoMobileStarter.MOBILE_NODE_NAME);		
		resourceManagersList.add(resourceManagerMobileAgent);
		
		// impostazione della QoS dei collegamenti
		for(int i=0; i<resourceManagerNamesList.size();i++){
			for(int j=0; j<resourceManagerNamesList.size();j++){
				status.addNewLink(resourceManagerNamesList.get(i), resourceManagerNamesList.get(j), new Integer(r.nextInt(79)+20));
			}
		}
		
		// impostazione dello stato del poltergeist
		poltergeist.setStatus(status);
		
		// creazione dei componenti
		int maxCpu = Math.round( (float)NodeType1.NOMINAL_CPU/3*2);
		int maxMem = Math.round( (float)NodeType1.NOMINAL_MEMORY/3*2);
		int minCpu = Math.round( (float)NodeTypeM.NOMINAL_CPU/2);
		int minMem = Math.round( (float)NodeTypeM.NOMINAL_MEMORY/2);
		for(int i=0;i<NUM_COMPONENTS - 20;i++) {			
			Component component = new Component();
			/*int reqCPU = r.nextInt(Math.round(350)) + 1;
			/int reqMEM = r.nextInt(Math.round(60)) + 1;
			int type = r.nextInt(101);
			int reqCpu = 0;
			int reqMem = 0;
			if(type <= 10){
				reqCpu = r.nextInt(Math.round(NodeType1.NOMINAL_CPU/3))+1;
				reqMem = r.nextInt(Math.round(NodeType1.NOMINAL_MEMORY/3))+1;								
			} else if(type > 10 && type <= 40 ){
				reqCpu = r.nextInt(Math.round(NodeType2.NOMINAL_CPU/3))+1;
				reqMem = r.nextInt(Math.round(NodeType2.NOMINAL_MEMORY/3))+1;
			} else if(type > 40 && type <= 70 ){
				reqCpu = r.nextInt(Math.round(NodeType3.NOMINAL_CPU/3))+1;
				reqMem = r.nextInt(Math.round(NodeType3.NOMINAL_MEMORY/3))+1;
			} else if(type > 70 && type <= 80 ){
				reqCpu = r.nextInt(Math.round(NodeType4.NOMINAL_CPU/3))+1;
				reqMem = r.nextInt(Math.round(NodeType4.NOMINAL_MEMORY/3))+1;
			} else{
				reqCpu = r.nextInt(Math.round(NodeTypeM.NOMINAL_CPU/3))+1;
				reqMem = r.nextInt(Math.round(NodeTypeM.NOMINAL_MEMORY/3))+1;
			}*/
			int reqCpu = randomInIterval(minCpu, maxCpu);
			int reqMem = randomInIterval(minMem, maxMem);
			component.setRequiredCPU(reqCpu);
			component.setRequiredMemory(reqMem);
			componentsList.add(component);
			status.addComponentRequiredCPU("Comp." + i, reqCpu);
			status.addComponentRequiredMemory("Comp." + i, reqMem);
		}
		// creazione componenti per il nodo mobile (20% dei componenti eseguibili sicuramente sul nodo mobile)
		maxCpu = Math.round( (float)NodeTypeM.NOMINAL_CPU);
		maxMem = Math.round( (float)NodeTypeM.NOMINAL_MEMORY);
		minCpu = Math.round( (float)NodeTypeM.NOMINAL_CPU/3);
		minMem = Math.round( (float)NodeTypeM.NOMINAL_MEMORY/3);
		for (int i=80; i<100; ++i) {
			Component component = new Component();
			int reqCpu = randomInIterval(minCpu, maxCpu);
			int reqMem = randomInIterval(minMem, maxMem);
			component.setRequiredCPU(reqCpu);
			component.setRequiredMemory(reqMem);
			componentsList.add(component);
			status.addComponentRequiredCPU("Comp." + i, reqCpu);
			status.addComponentRequiredMemory("Comp." + i, reqMem);
		}
		
		//allocazione del poltergheist sul main container
		AgentController poltergeistController = agentContainersList.get(0).acceptNewAgent("Poltergeist", poltergeist);
		agentControllersList.add(poltergeistController);
		
		//allocazione dei resourceManager fissi nei container per i nodi fissi
		for(int i=0;i<resourceManagersList.size()-1;i++){			
			AgentController controller = agentContainersList.get(i+1).acceptNewAgent("Node."+i, resourceManagersList.get(i));
			agentControllersList.add(controller);
		}
				
		//allocazione del resourceManager del nodo mobile nel container del nodo mobile
		AgentController mobileResourceManagerController = agentContainersList.get(agentContainersList.size()-1).acceptNewAgent("Node.M", resourceManagersList.get(resourceManagersList.size()-1));
		agentControllersList.add(mobileResourceManagerController);
		
		//allocazione del vampiro sul nodo mobile
		AgentController powerVampireAgentController = agentContainersList.get(agentContainersList.size()-1).acceptNewAgent("vampire", powerVampire);
		agentControllersList.add(powerVampireAgentController);
		
		//allocazione casuale dei componenti sui nodi		
		for(int i=0;i<componentsList.size();i++) {
			int position = -1;
			boolean insufficientResource = true;
			// controllo che il componente viene allocato su un nodo che ha risorse sufficienti
			while (insufficientResource) {
				position = r.nextInt(TOTAL_NODES)+1;
				//Logger.log("[STARTUP] ResourceManagersList size: " + resourceManagersList.size());
				//Logger.log("[STARTUP] AgentContainersList size: " + agentContainersList.size());
				ResourceManager rm = resourceManagersList.get(position-1);
				if((componentsList.get(i).getRequiredCPU() < rm.getNOMINAL_CPU()) &&
						(componentsList.get(i).getRequiredMemory() < rm.getNOMINAL_MEMORY())) {
					AgentController controller = agentContainersList.get(position).acceptNewAgent("Comp."+ i, componentsList.get(i));
					try {
						//Logger.log("[STARTUP] TOTAL_NODES: " + TOTAL_NODES);
						//Logger.log("[STARTUP] Extracted position: " + position);
						//Logger.log("[STARTUP] At index 0 i have: " + agentContainersList.get(TOTAL_NODES).getContainerName());
						
						//Logger.log("[STARTUP] Assigning component Comp." + i + " to node " + agentContainersList.get(position).getContainerName());
						status.moveComponent("Comp." + i, agentContainersList.get(position).getContainerName());
					} catch (ControllerException e) {
						e.printStackTrace();
					}
					agentControllersList.add(controller);
					insufficientResource = false;
				} 
			}					
		}		
		
		//avvio di tutti gli agenti
		for (int i=1;i<agentControllersList.size();i++)
			agentControllersList.get(i).start();			
		// avvia poltergeist after 2000 ms
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		agentControllersList.get(0).start();
	}
	
	public static int randomInIterval(int min, int max){
		return r.nextInt((max+1-min))+min;
	}
}
